# Instalar y cargar los paquetes
library(waffle)
# Datos
dataset <- read.csv("datasets\\Top_1000_wealthiest_people_d4.csv")
#Se convierten los datos a un vector nombrado
valores <- (setNames(dataset$Net.Worth..in.billions., dataset$Company))
# Crear el gráfico de waffle
waffle_chart <- waffle(valores, rows = 10, size =1,
colors = rainbow(length(valores))) +
labs(title = "Distribución de la riqueza mundial por empresas", subtitle = "Gráfico de waffle de 100 unidades")
# Mostrar el gráfico
waffle_chart30DaysChanllenge
Categoría: “Comparisons”
Día 1: Part-to-Whole
Audiencia: Grupo de estudiantes de economía
Objetivo: Mostrar los sectores industriales donde se genera más riqueza con todas las posibilidades de estrategia que ello conlleva, como la inversión o el estudio de un sector u otro.
Visualización: Herramienta Datawrapper para realizar un gráfico de rosquilla, convirtiendo los valores netos a porcentajes.
Conjunto de datos: Se realiza una agregación por sector industrial sumando los valores netos por cada registro. La fuente de datos es la siguiente: https://www.kaggle.com/datasets/muhammadehsan02/top-1000-wealthiest-people-in-the-world/data
Día 2: Neo
Audiencia: Grupo de estudiantes de economía
Objetivo: Informar sobre quiénes son las personas que dominan el planeta económicamente y las cantidades de sus riquezas.
Visualización: Herramienta Datawrapper para realizar un gráfico de barras horizontal.
Conjunto de datos: De nuevo, el origen de los datos es el mismo que antes: https://www.kaggle.com/datasets/muhammadehsan02/top-1000-wealthiest-people-in-the-world/data. Se realiza una agregación por nombre de persona y sumando sus riquezas, las cuales pueden proceder de diferentes sectores industriales y, por lo tanto, la misma persona puede aparecer en más de un registro.
Día 3: Makeover
Audiencia: Grupo de estudiantes de economía
Objetivo: Mostrar los sectores industriales donde se genera más riqueza con todas las posibilidades de estrategia que ello conlleva, como la inversión o el estudio de un sector u otro.
Visualización: En esta ocasión, se crea un gráfico muy visual y espectacular usando IA Generativa en base a la gráfica del primer día. El gran inconveniente de este tipo de herramientas para la creación de gráficas es el bajo control que se tiene sobre lo que aparece en la imagen por mucho que se refina el prompt. Por ejemplo, se ha distorsionado algunas etiquetas de sectores así como los valores asociados. Es decir, es una herramienta muy potente y espectacular pero aún mejorable desde el punto de vista del control.
Conjunto de datos: Se utiliza como prompt la imagen gráfica del día 1 (Part-to-whole) además del siguiente texto: “Create an image of a pie chart with an original style, including 3D figures related to each industrial sector. The sectors should be represented as follows: Technology (35%) with images such as computers or smartphones, Retail (23%) with images like shopping bags or a storefront, Manufacturing (10%) with icons like gears or a factory, Telecommunications (6%) with symbols such as satellite dishes or cell towers, Media (6%) with elements like film reels or cameras, and Other (22%) with assorted icons that represent various industries. Include the labels with words of sectors and percentages and ensure that they are clear and undistorted.”
Día 4: Waffle
Audiencia: Grupo de estudiantes de economía
Objetivo: Mostrar la distribución de empresas que generan más riquezas a las personas más ricas del planeta. Cuanto más riqueza genera, más cuadrados de color tiene asociado.
Visualización: Se utiliza un waffle chart utilizando la librería de R “waffle”.
Conjunto de datos: El origen de los datos sigue siendo el mismo: https://www.kaggle.com/datasets/muhammadehsan02/top-1000-wealthiest-people-in-the-world/data. En esta ocasión, se realiza una agregación por empresas sumando el valor de la riqueza. Además, se normalizan los datos calculando la proporción sobre 100 de cada empresa y convirtiéndolas en un número entero para obtener este tipo de dato, necesario en tipo y forma para este tipo de gráfica.
Día 5: Diverging
library(ggplot2)
# Datos de ejemplo
datos <- read.csv("datasets\\Inequality in Income Spain Comparison_d5.csv")
datos$Desigualdad <- ifelse(datos$Inequality.in.income..2021. > 0, "Mayor desigualdad que España", "Menor desigualdad que España")
# Crear el gráfico
ggplot(data = datos, aes(x = reorder(Country, Inequality.in.income..2021.),
y = Inequality.in.income..2021.,
fill = Desigualdad)) +
geom_bar(stat = "identity", width = 0.5, color = "black") +
coord_flip() +
scale_fill_manual(values = c("pink", "lightblue")) +
labs(title = "¿Qué países enfrentan una mayor desigualdad de ingresos en comparación con España?",
x = "País",
y = "Diferencia de GINI con respecto a España",
fill = "Diferencia de desigualdad en ingresos") +
theme_minimal() +
theme(panel.spacing = unit(100, "lines"),
plot.title = element_text(size = 9, face = "bold")) Audiencia: Grupo de estudiantes de economía
Objetivo: Concienciar de la posición que ocupa España con respecto al resto de países del mundo en relación a la desigualdad de ingresos de sus habitantes.
Visualización: Se utiliza la librería “ggplot2” de R. El gráfico elegido es un gráfico horizontal de barras modificando los colores en función de si es mejor o peor el resultado en comparación con España.
Conjunto de datos: https://www.kaggle.com/datasets/iamsouravbanerjee/inequality-in-income-across-the-globe. Se modifica la columna “Inequality in income (2021)”, donde se muestra el GINI por país, para calcular cada una con respecto a España, restándole el valor GINI de España a cada valor GINI del resto de los países. De esta manera, se consigue el efecto deseado y se puede realizar una comparación con el país de interés.
Día 6: OECD (data day)
Audiencia: Grupo de estudiantes de economía
Objetivo: Resaltar aquellos continentes con una mayor desigualdad en cuanto a ingresos en sus países.
Visualización: Se realiza a través de PowerBI. Se usa una gráfica de embudo con un degradado de colores para mostrar los resultados de “peor” (mayor GINI promedio) a “mejor” (menor GINI promedio).
Conjunto de datos: Se usa el mismo dataset del día anterior: https://www.kaggle.com/datasets/iamsouravbanerjee/inequality-in-income-across-the-globe. En esta ocasión, se ha realizado el promedio de todos los paises por continente y se ha ordenado de mayor a menor.
Categoría: “Distributions”
Día 7: Hazards
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Se carga el dataset
df = pd.read_csv("datasets//annual_deaths_by_causes.csv")
# Se realizan las transformaciones adecuadas para la correcta visualización
df = df[df.code != "OWID_WRL"]
cols_causes = df.columns[3:]
df = pd.melt(df,id_vars = ['country', 'code'],value_vars = cols_causes,var_name = "cause_of_death",value_name = "number_of_people")
df = df.groupby('cause_of_death')['number_of_people'].sum().reset_index()
df.sort_values(by = 'number_of_people', ascending = False, inplace = True)
df.reset_index(drop = True, inplace = True)
# Cálculo de características del boxplot
Q1 = df.number_of_people.quantile(0.25)
Q3 = df.number_of_people.quantile(0.75)
IQR = Q3 - Q1
lim_sup = Q3 + IQR * 1.5
lim_inf = Q1 - IQR * 1.5
# Identifico los outliers
outliers = df[(df.number_of_people > lim_sup) | (df.number_of_people < lim_inf)]
# Se carga el tema "pastel" de sns
sns.set_theme(style="ticks", palette="pastel")
# Se pinta el boxplot
sns.boxplot(y="number_of_people", data=df, palette = ["g"], legend = False)
plt.title("Distribución de muertes mundiales acumuladas por tipo de enfermedad (1990-2019)")
# Etiqueto los outliers
for i, row in outliers.iterrows():
plt.text(-0.02, row.number_of_people, f'{row.cause_of_death}', color = 'black',
ha = 'right', va = 'center', fontsize = 8)
# Muestro la figura
plt.show()Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo: Se pretende mostrar la distribución de número acumulado de causas de muerte desde 1990 hasta 2019 con el fin de determinar si existen ciertas enfermedades que, en comparación con el resto, tienen una incidencia mucho más alta o baja.
Visualización: Se utiliza la librería matplotlib.pyplot junto con seaborn de python. Se escoge un gráfico boxplot, ideal para identificar visualmente outliers.
Conjunto de datos: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019. Previo al graficado de datos, se ha de calcular programáticamente los outliers para que puedan ser etiquetados en el boxplot. De esta manera, la audiencia puede identificar fácilmente aquellas enfermedades que están fuera de la normalidad.
Día 8: Circular
library(tidyr)
library(dplyr)
library(ggplot2)
library(ggrepel)
library(tidyverse)
#Obtengo el dataframe del csv
df <- read.csv("datasets\\annual_deaths_by_causes.csv")
#Extraigo todas las columnas de las causas de muerte
cols_causes <- names(df)[4:ncol(df)]
#Se transforma el dataframe
df_transformed <- df %>%
pivot_longer(cols = cols_causes,
names_to = "cause_of_death",
values_to = "number_of_people") %>%
filter(country == "Spain") %>%
group_by(cause_of_death) %>%
summarise(total_people = sum(number_of_people)) %>%
arrange(desc(total_people)) %>%
drop_na()
#Se añaden los porcentajes
total_sum <- sum(df_transformed$total_people)
df_transformed <- df_transformed %>%
mutate(percentage = round((total_people / total_sum)*100, 0))
#Se divide el df en dos partes: las primeras 5 filas y el resto
df_top5 <- df_transformed[1:5, ]
df_others <- df_transformed[6:nrow(df), ]
#Sumar los valores de otros
df_others_summary <- df_others %>%
summarise(cause_of_death = "Others",
total_people = sum(total_people, na.rm = TRUE),
percentage = sum(percentage, na.rm = TRUE))
#Se combinan los primeros 5 valores y el grupo "Otros"
df_final <- bind_rows(df_top5, df_others_summary)
#Se realiza un gráfico de sectores con etiquetas fuera del gráfico
# Obtener las posiciones
df2 <- df_final %>%
mutate(csum = rev(cumsum(rev(percentage))),
pos = percentage/2 + lead(csum, 1),
pos = if_else(is.na(pos), percentage/2, pos))
ggplot(df_final, aes(x = "" , y = percentage, fill = fct_inorder(cause_of_death))) +
geom_col(width = 1, color = 1) +
coord_polar(theta = "y") +
scale_fill_brewer(palette = "Pastel1") +
geom_label_repel(data = df2,
aes(y = pos, label = paste0(percentage, "%")),
size = 4.5, nudge_x = 1, show.legend = FALSE) +
guides(fill = guide_legend(title = "Grupo")) +
ggtitle("Distribución de Causas de Muerte en España") +
theme_void() Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo: Focalizar los esfuerzos de investigación para las enfermedades más relevantes
Visualización: Se utiliza ggplot2 con la librería ggrepel. Con esto se consigue un gráfico de sectores con el etiquetado fuera del propio gráfico con una visualización, como resultado, más clara.
Conjunto de datos: Origen: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019. Se necesita transformar el dataset original cambiando la estructura, pasando las columnas de las enfermedades a una sola columna como “causes_of_death” y sus valores a otra columna llamada “number_of_people”. A partir de aquí, se realizan transformaciones varias de limpieza, agregaciones y sumas, para obtener los valores mostrados. Se comprueba que España tiene una distribución similar a la mundial, dado que las tres grandes enfermedades que afectan a este país son las mismas.
Día 9: Major/Minor
Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo: Mostrar los países con mayor y menor incidencia en muertes debido a neoplasia (cáncer) en 2019 con el fin de reflexionar sobre qué puede llevar a dichos países a alcanzar estas cifras. Visualización: PowerBI. Se escoge dos visualizaciones de tipo Tarjeta para mostrar el país así como la cifra de incidencias por cada 100 mil habitantes.
Conjunto de datos: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019, https://worldpopulationreview.com/. Para poder realizar dicha visualización, con PowerBI se importan los datos preprocesados desde Python realizando un derretido de las columnas de enfermedades. Luego, se crean campos calculados para mostrar los datos deseados, el país con mayor/menor número de muertes por cada 100 mil habitantes y la cifra. Además, se añaden ciertos filtros para que solo se tenga en cuenta la enfermedad de neoplasia y el año 2019 (fecha más actual de la que se tienen datos).
Día 10: Physical
Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo: Transmitir una visión general de la distribución de afección de la enfermedad con la incidencia más alta de muertes en el planeta.
Visualización: Datawrapper. Así como los mapas físicos muestran con colores las propiedades físicas de un mapa, se utiliza el mismo concepto para mostrar la incidencia de muertes por enfermedades cardiovasculares por país (por cada 100 mil habitantes).
Conjunto de datos: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019, https://worldpopulationreview.com/. Para poder realizar dicha visualización, primero se preprocesan los datos con Python realizando un derretido de las columnas de enfermedades. Luego, se importan los datos a PowerBI, donde se realizará una conexión entre dos tablas de dos fuentes de datos diferentes (mencionadas anteriormente). Estas tienen como campo identificativo el país. A partir de aquí, se puede añadir la cantidad de habitantes por país y realizar el cálculo de número de muertes por cada 100 mil habitantes. Además, se añaden ciertos filtros para que solo se tenga en cuenta las enfermedades cardiovasculares y el año 2010 (fecha más actual de la que se tienen datos para combinar en ambos datasets). Una vez se preprocesan los datos, se importan a Datawrapper para poder crear el mapa interactivo y poder embeberlo en RStudio con Quarto.
Día 11: Mobile-friendly
Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo:
Visualización: Datawrapper. Se usa esta herramienta por tener la opción de embeber de forma interactiva y, además, hacerlo adaptativo para móviles. Para ello, se ha optado por una visualización en tabla con degradado por gravedad de incidencia. Para hacerlo “mobile-friendly” se activan las opciones “Mobile fallback” y “Compact layout”. Además, se le da interactividad mediante la activación de búsqueda de países mediante la opción “Make searchable”.
Conjunto de datos: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019, https://worldpopulationreview.com/. Para esta visualización, se aprovecha todo el procesamiento de las gráficas anteriores con la diferencia que se ha filtrado por una enfermedad distinta, “chronic_respiratory_diseases”, que es la tercera con más incidencia en el planeta.
Día 12: Reuters Graphics (theme day)
Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo: Reflexionar sobre qué es lo que está ocurriendo en el mundo para que el número de fallecidos mundiales a causa de ciertas enfermedades haya aumentado en las últimas décadas.
Visualización: Datawrapper. Se usa un gráfico de líneas para visualizar la evolución de las enfermedades. Al usar esta herramienta embebida, se da la posibilidad a la audiencia de revisar los datos de forma interactiva.
Conjunto de datos: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019. Se preparan los datos eliminando las columnas de “country” y de “code”. Además, se realiza una agrupación por “year” para graficar la evolución. Se calcula la media del ratio de crecimiento de la población mundial en base a https://worldpopulationreview.com/.
Categoría: “Relationships”
Día 13: Family
#Se usará la librería igraph
library(dplyr)
library(ggplot2)
# Se carga el df
df <- read.csv('datasets\\used_cars_data.csv')
#Se seleccionan las instancias a graficar
df1 <- select(df, Year, Fuel_Type, Price)
#Se eliminan valores faltantes
df1 <- na.omit(df1)
#Se grafica la cantidad de datos existentes según el tipo de Fuel
library(ggthemes)
library(patchwork)
library(scales)
ggplot(df1, aes(Year, fill = Fuel_Type)) +
geom_bar(position = 'fill') +
scale_y_continuous(labels = percent) +
theme_solarized() +
plot_annotation(title = "¿Qué tipo de coche según el tipo de Fuel predomina más?",
caption = "Cristian Guerrero Balber| Datos: used_cars_data.csv (kaggle)")Audiencia: Conferencia sobre Energía sostenible
Objetivo: Mostrar la cantidad de instancias por familia de Fuel. Esta visualización quiere mostrar el reflejo de la proporción de coches según el Fuel que existen (ojo, esto puede setar sesgado por la fuente de datos).
Visualización: Se utiliza la de la librería ggplot de R. Se usa la extensión ggtheme para darle un aspecto visual atractivo y original. Además, se usa el gráfico de barras apiladas para mostrar la proporción de instancias optimizando la cantidad de código.
Conjunto de datos: Se procesan los datos simplemente seleccionando las características a vincular y limpiándo el df resultante de nulos. Fuente: https://www.kaggle.com/datasets/ayushparwal2026/cars-dataset/data
Día 14: Heatmap
#Se usará la librería ggplot2
library(ggplot2)
library(dplyr)
library(reshape2)
library(ggthemes)
# Se carga el df
df <- read.csv('datasets\\used_cars_data.csv')
#Se seleccionan las características a comparar
df1 <- select(df, Year, Kilometers_Driven, Fuel_Type, Transmission,
Owner_Type, Seats, Price)
#Se realiza una codificación de características no numéricas
# Supongamos que df es tu dataset y 'columna_categorica' es una columna no numérica
df1$Fuel_Type <- as.numeric(as.factor(df1$Fuel_Type))
df1$Transmission <- as.numeric(as.factor(df1$Transmission))
df1$Owner_Type <- as.numeric(as.factor(df1$Owner_Type))
#Se calcula la matriz de correlaciones
df_corr <- cor(df1, use = "complete.obs")
#Se convierte la matriz a formato largo
df_corr <- melt(df_corr)
# Crear el gráfico de la matriz de correlaciones
ggplot(data = df_corr, aes(x = Var1, y = Var2, fill = value)) +
geom_tile() +
scale_fill_gradient2(low = "blue", high = "red", mid = "white",
limit = c(-1, 1), name = "Coef. de Pearson") +
geom_text(aes(label = round(value, 2)), # Redondear y mostrar los valores
color = "black", # Color del texto
size = 4) +
theme_pander() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
labs(title = "¿Qué característica tiene mayor correlación sobre el precio?",
x = "Características",
y = "Características",
caption = "Cristian Guerrero Balber| Datos: used_cars_data.csv (kaggle)") +
theme(plot.title = element_text(size = 10))Audiencia: Conferencia sobre Energía sostenible Objetivo: Mostrar las diferentes correlaciones entre distintas características y el precio. Concretamente, y en el tipo de conferencia que es, parece ser que el tipo de fuel y el precio no tiene una correlación lineal demasiado alta, mientras que la característica que tiene una mayor correlación con el precio es el tipo de transmisión. Visualización: Se utiliza un heatmap de ggplot2 en R Conjunto de datos: Se procesan los datos seleccionando las características a correlacionar. Se codifica en forma de número las columnas que no tienen un valor numérico. Luego, se obtiene la matriz de correlaciones y, por último, se grafica. Fuente: https://www.kaggle.com/datasets/ayushparwal2026/cars-dataset/data
Día 15: Historical
#Se usará la librería igraph
library(dplyr)
library(ggplot2)
# Se carga el df
df <- read.csv('datasets\\used_cars_data.csv')
#Se seleccionan las instancias a graficar
df1 <- select(df, Year, Fuel_Type, Price)
#Se eliminan valores faltantes
df1 <- na.omit(df1)
#Se itera entre los distintos tipos de fuel para ver si hay una cantidad
#suficientemente representativa de cada uno para graficar
fuel_types <- c(unique(df$Fuel_Type))
for (fuel_type in fuel_types){
df_ <- df1 %>%
filter(Fuel_Type == fuel_type) %>%
select(Fuel_Type)
cat("% de instancias para", fuel_type, ":",
round((nrow(df_) / nrow(df1)) * 100, 2), "%\n")
}% de instancias para CNG : 0.93 %
% de instancias para Diesel : 53.25 %
% de instancias para Petrol : 45.62 %
% de instancias para LPG : 0.17 %
% de instancias para Electric : 0.03 %
library(ggthemes)
library(patchwork)
#Se filtra solo por los más representativos
df2 <- df1 %>%
filter(Fuel_Type == 'Diesel' | Fuel_Type == 'Petrol')
ggplot(df2, aes(Year, Price, colour = Fuel_Type)) +
geom_point() +
stat_smooth(method = 'lm') +
scale_x_log10() +
scale_y_log10() +
theme_solarized(light = FALSE) +
labs(title = "¿Qué me costará más, Diesel o Gasolina (Petrol)?",
caption = "Cristian Guerrero Balber| Datos: used_cars_data.csv (kaggle)")Audiencia: Conferencia sobre Energía sostenible
Objetivo: Mostrar la evolución del precio en los últimos años de los coches de tipo Diesel y Gasolina.
Visualización: Se usa RStudio con ggplot, con un estilo de la extensión ggthemes. Además, se visualiza por colores los distintos tipos de Fuel. Se ha usado la transformación logaritmica para minimizar el efecto visual de los outliers.
Conjunto de datos: Se procesan los datos seleccionando las características a graficar. Se seleccionan los tipos de fuel más representativos en el dataset. Fuente: https://www.kaggle.com/datasets/ayushparwal2026/cars-dataset/data
Día 16: Weather
#Se usará la librería igraph
library(dplyr)
library(ggplot2)
# Se carga el df
df <- read.csv('datasets\\2021.Vans_Aggregated.csv')
#Se grafica la cantidad de datos existentes según el tipo de Fuel
library(ggthemes)
ggplot(df, aes(Fuel.Type, WLTP.CO2.emissions.weighted..g.km., fill = Fuel.Type)) +
geom_violin() +
geom_boxplot(width = 0.05) +
theme_economist() +
scale_fill_discrete(labels = c('Diesel', 'Gasolina', 'Híbrido'),
name = 'Tipo de combustible') +
theme(axis.text.x = element_blank(),
legend.position = 'right') +
labs(y = "Emisiones CO2 emitidas en g/km",
title = "¿Qué tipo de coche según el Fuel predomina más?",
caption = "Cristian Guerrero Balber| Datos: real-world-vehicle-emissions.csv (kaggle)")Audiencia: Conferencia sobre Energía sostenible
Objetivo: Mostrar la gran diferencia de la distribución de emisiones de CO2
Visualización: RStudio con ggplot. Se usa un gráfico de violines junto con boxplots, que muestra no solo la distribución de datos sino la densidad de los mismos por categorías. Se muestra claramente como la distribución de las emisiones de los coches híbridos es, de media, 3 veces más baja que las de diesel y gasolina. Además, la varianza de los datos es menor con cero outliers y bigotes de boxplots muy cortos.
Conjunto de datos: No se realiza preprocesado de los datos salvo su carga y su visualización, ya que este tipo de visualización incorpora el propio tratamiento de los datos. Fuente: https://www.kaggle.com/datasets/konradb/real-world-vehicle-emissions
Día 17: Networks
#Se usará la librería igraph
library(igraph)
library(dplyr)
# Se carga el df
df <- read.csv('datasets\\used_cars_data.csv')
#Se seleccionan las instancias a graficar
df1 <- select(df, Fuel_Type, Price)
#Se discretiza la columna de precio en cuartiles
df1 <- df1 %>%
mutate(Discreted_price = ntile(Price, 4)) %>%
select(-Price) %>% #Se elimina la columna Price
mutate(Discreted_price = recode(Discreted_price,
"1" = "Bajo",
"2" = "Bajo-Medio",
"3" = "Medio-Alto",
"4" = "Alto"
) )
#Se eliminan duplicados
df1 <- na.omit(unique(df1))
# Crear el gráfico de red usando igraph
grafo <- graph_from_data_frame(df1, directed = FALSE)
layout <- layout_with_sugiyama(grafo)
# Configurar los colores de los nodos
V(grafo)$color <- ifelse(V(grafo)$name %in%
c("Bajo", "Bajo-Medio", "Medio-Alto", "Alto"), "salmon", "lightgreen")
# Dibujar el gráfico de la familia
plot(grafo,
layout = layout,
vertex.size = 50,
vertex.label.cex = 0.8,
vertex.label.color = "black", # Color del texto
vertex.frame.color = NA, # Sin borde en los nodos
main = "Red Familia Precios")
legend("bottomleft",
legend = c("CNG = Compressed Natural Gas",
"LPG = Gas Licuado de Petróleo"),
cex = 0.8,
bty = 'n')Audiencia: Conferencia sobre Energía sostenible
Objetivo: Mostrar relaciones en forma de red de precios según el tipo de fuel. Concretamente, concienciar que no existen conexiones entre la energía eléctrica y los coches de precios asequibles según este dataset.
Visualización: Se utiliza la de la librería igraph de R un tipo de gráfico concreto para establecer relaciones en redes. Ideal para mostrar relaciones entre tipo o familias de características.
Conjunto de datos: Se procesan los datos seleccionando las características a vincular y limpiándo el df resultante de nulos. Además, se discretiza la columna Price para obtener una escala cualitativa del precio y poder relacionarla con el tipo de fuel. Fuente: https://www.kaggle.com/datasets/ayushparwal2026/cars-dataset/data
Día 18: Asian Development Bank (data day)
#Se usará la librería igraph
library(dplyr)
library(ggplot2)
library(lubridate)
library(tvthemes)
# Se carga el df
df <- read.csv('datasets\\ADB Climate Change Financing - 2023-based on commitments.csv')
#Se transforma la columna de fecha de texto a fecha, y se limpia el df de nulos
df1 <- df %>%
select(Date.Signed, Sector, Signed.amount....million.) %>%
mutate(Signed.amount = suppressWarnings(as.numeric(Signed.amount....million.))) %>%
select(-Signed.amount....million.)%>%
mutate(Date.Signed = parse_date_time(Date.Signed, orders = 'd-b-y')) %>%
mutate(Date.Signed = as.Date(Date.Signed))%>%
arrange(Date.Signed) %>%
na.omit()
#Se cambia la fecha a semanas
df1 <- df1 %>%
mutate(Week.Signed = floor_date(Date.Signed, "week"))%>%
group_by(Week.Signed, Sector) %>%
summarise(Signed.amount = sum(Signed.amount)) %>%
ungroup()
#Se calcula el acumulado por semana y sector
df1 <- df1 %>%
group_by(Sector) %>%
mutate(Signed.amount.cumsum = cumsum(Signed.amount)) %>%
ungroup()
#Se grafica la cantidad de datos existentes según el tipo de Fuel
library(ggthemes)
ggplot(df1, aes(Week.Signed, Signed.amount.cumsum, fill = Sector))+
geom_area(alpha = 0.8, colour = 'darkblue') +
theme_simpsons(title.font = "Lucida Sans Unicode",
text.font = "Akbar")+
scale_x_date(breaks = seq(min(df1$Week.Signed), max(df1$Week.Signed), "1 month"),
date_labels = "%b")+
scale_y_continuous(labels = scales::dollar)+
scale_fill_simpsons() +
labs(y = "Acumulador en millones de $ americanos",
x = "2023",
title = "¿Qué sector está marcando la diferencia en la lucha contra el cambio climático?",
caption = "Cristian Guerrero Balber| Datos: used_cars_data.csv (kaggle)")Audiencia: Conferencia sobre Energía sostenible
Objetivo: Mostrar las principales áreas económicas que aportan financiación en la lucha contra el cambio climático según un acuerdo firmado en 2015 por la Asian Development Bank (ADB).
Visualización: Se usa RStudio con ggplot. Se importa un estilo muy original de películas, en este caso, de los simpson, para mostrar unas áreas apiladas por sector de la aportación en dólares americanos.
Conjunto de datos: Se preprocesan los datos limpiando de nulos, agrupando por fechas y sectores, así como calculando los acumulados aportados por sector y fecha. Fuente: https://data.adb.org/dataset/climate-change-financing-adb
Categoría: “Timeseries”
Día 19: Dinosaurs
import pandas as pd
from pathlib import Path
ruta = "datasets//dinosaurs.csv"
dataset = pd.read_csv(ruta)
#Se muestra info de la tabla
print(dataset.info())<class 'pandas.core.frame.DataFrame'>
RangeIndex: 309 entries, 0 to 308
Data columns (total 10 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 name 309 non-null object
1 diet 309 non-null object
2 period 309 non-null object
3 lived_in 308 non-null object
4 type 309 non-null object
5 length 291 non-null object
6 taxonomy 309 non-null object
7 named_by 309 non-null object
8 species 304 non-null object
9 link 309 non-null object
dtypes: object(10)
memory usage: 24.3+ KB
None
#Existen variables como lived_in, length y species con nulos. Como se va a usar lengh para graficar, se eliminan los nulos
dataset = dataset.dropna(axis = 0)
#Se analizan los diferentes tipos de dinosaurios según su dieta
print(f"Tipos de dinosaurios según su dieta:")Tipos de dinosaurios según su dieta:
dataset['diet'].unique()array(['herbivorous', 'carnivorous', 'omnivorous', 'unknown',
'herbivorous/omnivorous'], dtype=object)
#El resultado 'herbivorous/omnivorous' parece una incongruencia, ya que si es onminoro entonces es herbivoro y carnivoro. Veamos cuantos registros hay.
print(f"\nDinosaurios de tipo herbivorous/omnivorous:")
Dinosaurios de tipo herbivorous/omnivorous:
dataset[dataset['diet'] == 'herbivorous/omnivorous'] name ... link
237 riojasaurus ... https://www.nhm.ac.uk/discover/dino-directory/...
[1 rows x 10 columns]
#Además, veamos los unknown
#Como hay pocos registros, se eliminan
dataset = dataset[(dataset['diet']!='herbivorous/omnivorous')&(dataset['diet']!='unknown')]
#Se verifica el resultado
dataset['diet'].unique()array(['herbivorous', 'carnivorous', 'omnivorous'], dtype=object)
#Por inspección visual de la columna "period", se comprueba que tiene casi siempre la misma estructura
dataset['period'].head()0 Early Jurassic 199-189 million years ago
1 Late Cretaceous 74-70 million years ago
2 Late Cretaceous 83-70 million years ago
3 Late Cretaceous 99-84 million years ago
4 Early Cretaceous 115-105 million years ago
Name: period, dtype: object
#La estructura: premura del periodo + periodo + rango(x-y) + "million years ago". Se crea una función para descomponerla y obtener los años de comienzo y fin del periodo
# ¿Son todas así?
dataset['split_parts'] = dataset['period'].apply(lambda x: x.split())
dataset['count_splits'] = dataset['split_parts'].apply(lambda x: len(x))
dataset[dataset['count_splits'] < 6] name diet ... split_parts count_splits
100 erketu herbivorous ... [Early, Cretaceous] 2
209 pantydraco herbivorous ... [Early, Jurassic] 2
[2 rows x 12 columns]
#Se filtra solo por los registros con 6 elementos después del split (sigue la estructura tipo)
dataset = dataset[dataset['count_splits'] == 6]
#Se crean las columnas de forma adecuada
dataset['stage'] = dataset['split_parts'].apply(lambda x: x[0])
dataset['period_name'] = dataset['split_parts'].apply(lambda x: x[1])
dataset['range_period'] = dataset['split_parts'].apply(lambda x: x[2])
#Ahora, del range_period, hay que crear el periodo de comienzo y fin, pero hay que comprobar que sigue este formato
print("¿Existe algún registro de rango de período que no contenga el separador '-'?")¿Existe algún registro de rango de período que no contenga el separador '-'?
print(dataset[~dataset['range_period'].str.contains('-')].shape)(25, 15)
#Existen registros sin fecha fin de periodo. Se eliminan estas entradas por estandarizar, ya que aun tenemos bastantes registros para graficar
dataset = dataset[dataset['range_period'].str.contains('-')]
#Se crean las columnas start_period y end_period
dataset['start_period'] = dataset['split_parts'].apply(lambda x: int(x[2].split("-")[0]))
dataset['end_period'] = dataset['split_parts'].apply(lambda x: int(x[2].split("-")[1]))
#Por último, la columna de "length" se va a cambiar a numérico
dataset['length'] = dataset['length'].apply(lambda x: float(x.replace("m", "")))
#Se ordena por length
dataset = dataset.sort_values(by = 'length', ascending = False)
#Se va a guardar el dataset para usarse en otro apartado
dataset.to_csv("datasets\\dinosaurs_mod.csv", index = False)
#Se definen los rangos de los periodos
start_periods = dataset.groupby('period_name')['start_period'].max()
end_periods = dataset.groupby('period_name')['start_period'].min()
print("Rangos de los períodos:")Rangos de los períodos:
print(start_periods)period_name
Cretaceous 154
Jurassic 208
Triassic 227
Name: start_period, dtype: int64
print(end_periods)period_name
Cretaceous 67
Jurassic 79
Triassic 205
Name: start_period, dtype: int64
#Como estos períodos deberían ser secuenciales, se va a calcular el punto medio entre periodos solapados para marcar la frontera
mean_point = int((end_periods['Triassic'] + start_periods['Jurassic']) / 2)
end_periods['Triassic'] = mean_point
start_periods['Jurassic'] = mean_point
mean_point = int((end_periods['Jurassic'] + start_periods['Cretaceous']) / 2)
end_periods['Jurassic'] = mean_point
start_periods['Cretaceous'] = mean_point
print("Rangos de los períodos tras procesarlos:")Rangos de los períodos tras procesarlos:
print(start_periods)period_name
Cretaceous 116
Jurassic 206
Triassic 227
Name: start_period, dtype: int64
print(end_periods)period_name
Cretaceous 67
Jurassic 116
Triassic 206
Name: start_period, dtype: int64
#Ahora se van a graficar los 5 dinosaurios más grandes de cada tipo según su dieta
import warnings
warnings.filterwarnings('ignore')
import plotly.graph_objects as go
def create_dinosaur_plot():
global dataset_filtered
global start_periods
global end_periods
# Crear la figura
fig = go.Figure()
#Colores de lineas por dieta
colors_diet = {
'carnivorous': 'rgba(255, 0, 0, 0.5)', # Rojo con 50% de transparencia
'herbivorous': 'rgba(0, 255, 0, 0.5)', # Verde con 50% de transparencia
'omnivorous': 'rgba(0, 0, 255, 0.5)', # Azul con 50% de transparencia
}
dataset_filtered = pd.concat([dataset[dataset['diet'] == 'carnivorous'].head(5),
dataset[dataset['diet'] == 'herbivorous'].head(5),
dataset[dataset['diet'] == 'omnivorous'].head(5)], axis = 0)
dataset_filtered = dataset_filtered.sort_values(by = 'length', ascending = True)
# Agregar las líneas horizontales (conexión entre puntos)
for i, row in dataset_filtered.iterrows():
category = row[1]
color = colors_diet[category]
fig.add_trace(go.Scatter(
x=[-row[15], -row[16]], # Coordenadas x para cada grupo (inicio, fin)
y=[row[5], row[5]], # Coordenadas y (mismo valor para conectar)
mode='lines',
line=dict(color=color, width=8),
showlegend = False
))
# Agregar puntos para la primera categoría (Inicio)
fig.add_trace(go.Scatter(
x=[-row[15]],
y=[row[5]],
text = [row[0]],
textposition = 'top center',
textfont = dict(family = 'Arial Bold'),
mode='markers+text',
name='Inicio Período',
marker=dict(color=color, size=10),
showlegend = False
))
# Agregar puntos para la segunda categoría (Fin)
fig.add_trace(go.Scatter(
x=[-row[16]],
y=[row[5]],
mode='markers',
name='Fin Período',
marker=dict(color=color, size=10),
showlegend = False
))
#Agregar un rectánculo para cada períodos
colors_period = {
'Cretaceous': 'rgb(165, 70, 87)',
'Jurassic': 'rgb(76, 106, 146)',
'Triassic': 'rgb(53, 94, 59)'
}
for period in start_periods.index:
fig.add_shape(
type = "rect",
x0 = -start_periods[period], x1 = -end_periods[period],
y0 = min(dataset_filtered['length']) - 5,
y1 = max(dataset_filtered['length']) + 5,
fillcolor = colors_period[period],
opacity = 0.3,
layer = "below",
line = dict(color = 'black', width = 3)
)
# Agregar texto a los rectángulos
fig.add_annotation(
x=(-start_periods[period] + -end_periods[period]) / 2, # Centro en el eje x
y=max(dataset_filtered['length']) + 4, # Top en el eje y
text=period,
showarrow=False,
font=dict(size=16, color=colors_period[period]), # Propiedades de la fuente
align='center' # Alineación del texto
)
# Agregar un trace para cada categoría en la leyenda
for categoria, color in colors_diet.items():
fig.add_trace(go.Scatter(
x=[None], # No hay coordenadas, solo se usa para la leyenda
y=[None],
mode='lines',
line=dict(color=color, width=4), # Color correspondiente
name=categoria, # Nombre que aparecerá en la leyenda
showlegend=True # Este trace aparecerá en la leyenda
))
# Configurar el layout
fig.update_layout(
title={
'text': '¿En qué período vivieron los dinosaurios más grandes que hayan pisado la faz de la Tierra?',
'font': {
'size': 15
}
},
xaxis_title='Millones de años atrás...',
yaxis_title='Metros de Altura',
width = 920,
height = 700
)
# Mostrar la gráfica
#fig.show()
return fig
fig = create_dinosaur_plot()
fig.show()Audiencia: Paleontólogos
Objetivo: Mostrar de forma clara los dinosaurios más grandes que han vivido en la tierra divididos por tipo de dieta (carnívoros, herbívoros y omnívoros) así como el período en el que existieron (triásico, jurásico y cetácico).
Visualización: Se usa la librería Plotly de Python. El tipo de gráfica escogida es una dumbbell plot. Se ha mostrado en sus ejes los metros de altura del dinosaurio contra los millones de años atrás en los que vivio. Cada dumbbell representa una franja de tiempo. Además, se ha dividido cada período por colores y se ha añadido una leyenda por color según el tipo de dieta del dinosaurio. Por último, se ha añadido el nombre del dinosaurio sobre su primer punto. De esta manera, se ha conseguido representar claramente hasta 5 dimensiones en un solo gráfico 2D.
Conjunto de datos: Ha sido necesario un prepreocesamiento moderado debido a que los datos de partida no tenian el formato adecuado (tratamiento de textos y números). Además, se ha tenido que modificar información sobre los comienzos de inicio y fin de períodos para que fuera coherente en el gráfico, por lo que esto ha podido alterar la veracidad e integridad de los mismos. Fuente:https://www.kaggle.com/datasets/kjanjua/jurassic-park-the-exhaustive-dinosaur-dataset
Día 20: Correlation
import pandas as pd
import matplotlib.pyplot as plt
ruta = "datasets/dinosaurs_mod.csv"
dataset = pd.read_csv(ruta)
dataset.columnsIndex(['name', 'diet', 'period', 'lived_in', 'type', 'length', 'taxonomy',
'named_by', 'species', 'link', 'split_parts', 'count_splits', 'stage',
'period_name', 'range_period', 'start_period', 'end_period'],
dtype='object')
dataset.drop(['name', 'period', 'type', 'lived_in', 'taxonomy', 'named_by', 'species', 'link', 'split_parts', 'count_splits', 'stage', 'range_period', 'start_period', 'end_period'], axis = 1, inplace = True)
#Se discretiza la longitud de los dinosaurios según sus cuartiles
dataset['length'] = pd.qcut(dataset['length'], q = 4, labels=['Pequeños', 'Medianos', 'Grandes', 'Muy grandes'])
agg = dataset.groupby(['diet', 'length']).size().reset_index(name = 'count')
def plotear_relacion():
global agg
# Mapear etiquetas a índices
all_labels = pd.concat([agg['diet'], agg['length']]).unique()
label_to_index = {label: index for index, label in enumerate(all_labels)}
# Crear listas para los nodos y los enlaces
source = agg['diet'].map(label_to_index).tolist() # Dietas
target = agg['length'].map(label_to_index).tolist() # Longitudes
values = agg['count'].tolist() # Conteos
# Asignar colores a cada tipo de dieta
diet_colors = {
'carnivorous': 'rgba(255, 0, 0, 0.8)', # Rojo para carnívoros
'herbivorous': 'rgba(0, 255, 0, 0.8)', # Verde para herbívoros
'omnivorous': 'rgba(0, 0, 255, 0.8)' # Azul para omnívoros
}
# Crear una lista de colores para cada enlace basado en la dieta
link_colors = [diet_colors[diet] for diet in agg['diet']]
# Crear el diagrama de Sankey
fig = go.Figure(go.Sankey(
node=dict(
pad=15,
thickness=20,
line=dict(color='black', width=0.5),
label=all_labels,
color='blue' # Color de los nodos
),
link=dict(
source=source, # Índices de los nodos de origen
target=target, # Índices de los nodos de destino
value=values, # Valores de los enlaces
color=link_colors # Colores de los enlaces según dieta
)
))
# Configurar el layout
fig.update_layout(
title={
'text': '¿Qué correlación existe entre el tamaño y la dieta?',
'font': {
'size': 15,
'family': 'monospace'
}})
return fig
#Llamar a la función
relacion = plotear_relacion()
# Mostrar el diagrama
relacion.show()Audiencia: Paleontólogos
Objetivo: Correlacionar la proporcionalidad existente entre el tipo de dieta del dinosaurio y su tamaño.
Visualización: Se usa la librería Plotly de Python. El tipo de gráfica escogida es una Sankey, correlacionando la cantidad de dinosaurios entre las categorías relacionadas por la anchura de las lineas conectoras y su color.
Conjunto de datos: Se ha necesitado realizar una discretización de los tamaños de los dinosaurios por sus cuartiles, luego una agregación de las columnas de interés y por último una creación de listas de etiquetas de “fuentes” y “objetivos” para crear la gráfica. Fuente:https://www.kaggle.com/datasets/kjanjua/jurassic-park-the-exhaustive-dinosaur-dataset
Día 21: Green Energy
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
#Añado estilo preestablecido de plt
plt.style.use('dark_background')
ruta = "datasets/intermittent-renewables-production-france.csv"
df = pd.read_csv(ruta)
print(df.info())<class 'pandas.core.frame.DataFrame'>
RangeIndex: 59806 entries, 0 to 59805
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date and Hour 59806 non-null object
1 Date 59806 non-null object
2 StartHour 59806 non-null object
3 EndHour 59806 non-null object
4 Source 59806 non-null object
5 Production 59804 non-null float64
6 dayOfYear 59806 non-null int64
7 dayName 59806 non-null object
8 monthName 59806 non-null object
dtypes: float64(1), int64(1), object(7)
memory usage: 4.1+ MB
None
#Solo hay dos instancias con nulos, se eliminan
df = df.dropna()
#Se realiza una agregación de la media por mes
df_grouped = df.groupby("monthName")['Production'].mean().reset_index()
# Ordenar los datos por el orden de los meses
month_order = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
df_grouped["monthName"] = pd.Categorical(df_grouped["monthName"], categories=month_order, ordered=True)
df_grouped = df_grouped.sort_values("monthName")
# Convertir los meses a ángulos para el gráfico polar
angles = np.linspace(0, 2 * np.pi, len(df_grouped), endpoint=False).tolist()
angles += angles[:1] # Repetir el primer ángulo para cerrar el ciclo
values = df_grouped["Production"].tolist()
values += values[:1] # Repetir el primer valor para cerrar el ciclo
# Configurar el gráfico polar
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
ax.plot(angles, values, linewidth=2, color = 'green', linestyle='solid')
ax.fill(angles, values, color = 'green', alpha=0.3)
# Ajustar etiquetas
ax.set_xticks(angles[:-1]) # Usar todos los ángulos excepto el último para evitar duplicados en las etiquetas
ax.set_xticklabels(df_grouped["monthName"])
# Configurar la cuadrícula
ax.grid(color='green', linestyle='--', linewidth=0.7, alpha=0.7) # Personaliza el color aquí
# Título y mostrar el gráfico
ax.set_title("Producción Media de Energía Verde Mensual 2020-2023", va='bottom')
plt.show()Audiencia: Skateholders de empresas manufactureras de vehículos eléctricos
Objetivo: Mostrar a inversores del sector eléctrico como se comporta la producción de energía verde para sus vehículos según el mes del año.
Visualización: Se usa Matplotlib de Python. El tipo de visualización es poligonal con un grid polar.
Conjunto de datos: Se crea una agrupación según el mes y se calcula la media de la producción. Tras esto, se sigue procesando ordenando según el mes y realizando diferentes transformaciones para adaptar los datos resultantes al tipo de gráfico que se busca.Fuente:https://www.kaggle.com/datasets/henriupton/wind-solar-electricity-production
Día 22: Mobility
import pandas as pd
import matplotlib.pyplot as plt
#Se reestablece el estilo
plt.style.use('default')
#Se carga el archivo
ruta = "datasets/intermittent-renewables-production-france.csv"
df = pd.read_csv(ruta)
#Solo hay dos instancias con nulos, se eliminan
df = df.dropna()
#Se ordena por la columna dayOfYear
df = df.sort_values(by = 'dayOfYear', ascending = True)
#Se transforma Date en fecha
df['Date'] = pd.to_datetime(df['Date'], format = "%Y-%m-%d")
#Se extrae el año
df['Year'] = df['Date'].dt.year
#Se realiza una agrupación y se calcula la media de la producción
df = df.groupby(['Source', 'dayOfYear', 'Year'])['Production'].mean().reset_index()
#Se crea una función para generar el plot
def create_plot():
global df
import seaborn as sns
sns.set_theme(style="ticks")
g = sns.relplot(x='dayOfYear', y='Production', hue = 'Source', size='Production',
sizes=(40, 400), alpha=.5, palette=['salmon', 'blue'],
height=6, col = 'Year', col_wrap = 2, data=df)
g.set_axis_labels("Día del Año", "Producción de Energía (MWh)")
g.fig.suptitle("Producción media de Energía a lo largo del año", y = 1.05)
plt.show()
create_plot()Audiencia: Skateholders de empresas manufactureras de vehículos eléctricos
Objetivo: Mostrar a los principales skateholders cuál es la mejor fuente de energía verde para mostrar la viabilidad de su negocio sin depender de combustibles fósiles dependiendo del momento del año.
Visualización: Se utiliza la librería Seaborn de Python junto con Matplotlib. El tipo de gráfico es de dispersión aprovechando las ventajas de la función relplot de Seaborn, que permite utilizar un FaceGrid muy cómodamente, además de agregar leyendas. En los distintos años, se observa una distribución de creación de energía similar, comprobando como la energía solar se equipara un poco más con la eólica a mitad de año. Sin embargo, en el resto de las épocas del año, la energía eólica es superior en la producción de energía.
Conjunto de datos: Se realiza un preprocesado transformando la columna de fecha en tipo de formato fecha y así poder extraer el mes, ya que no se tenía antes. A partir de aquí, se puede realizar una agregación múltiple incluyendo el mes, el cual será usado para crear el grid.Fuente:https://www.kaggle.com/datasets/henriupton/wind-solar-electricity-production
Día 23: Tiles
import pandas as pd
import matplotlib.pyplot as plt
#Se reestablece el estilo
plt.style.use('default')
#Se carga el dataframe
ruta = "datasets/intermittent-renewables-production-france.csv"
df = pd.read_csv(ruta)
#Solo hay dos instancias con nulos, se eliminan
df = df.dropna()
#Se ordena por la columna dayOfYear
df = df.sort_values(by = 'dayOfYear', ascending = True)
#Se transforma Date en fecha
df['Date'] = pd.to_datetime(df['Date'], format = "%Y-%m-%d")
#Se extrae el año
df['Year'] = df['Date'].dt.year
#Se realiza la agrupación por año y mes
df = df.groupby(['Year', 'monthName'])['Production'].mean().reset_index()
# Ordenar los datos por el orden de los meses
month_order = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
df["monthName"] = pd.Categorical(df["monthName"], categories=month_order, ordered=True)
df = df.sort_values("monthName")
#Se pivota para crear una tabla donde se crucen las columnas con sus valores
df = df.pivot(index = 'monthName', columns = 'Year', values = 'Production')
plt.figure()
#Se crea un heatmap
ax = sns.heatmap(df, annot = True, linewidths = .5, cmap = 'coolwarm')
# Configurar los nombres de los ejes
ax.set_xlabel('Años')
ax.set_ylabel('Meses')
# Se añade un título
ax.set_title("¿Cuándo se ha producido más energía verde en los últimos años?", fontsize=10, fontweight='bold', color='darkblue')
plt.show()Audiencia: Skateholders de empresas manufactureras de vehículos eléctricos
Objetivo: Mostrar a los principales skateholders cuáles han sido los mejores meses de los últimos años en la generación de energía a través de métodos sostenibles.
Visualización: Se ha escogido un heatmap de Seaborn de Python. De esta manera, se puede comprobar cuáles han sido los mejores meses de los últimos años en cuanto a la producción energética verde.
Conjunto de datos: Se realiza un preprocesado transformando la columna de fecha en tipo de formato fecha y así poder extraer el mes. A partir de aquí, se puede realizar una agregación múltiple incluyendo el mes, el cual será usado para crear el grid. Además, para preparar los datos para este tipo de gráfica, se pivota sobre las columnas de mes y año.Fuente:https://www.kaggle.com/datasets/henriupton/wind-solar-electricity-production
Día 24: ILO Region for Africa (data day)
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
#Se reestablece el estilo
plt.style.use('default')
#Se carga el dataframe
ruta = "datasets/SDG_0852_SEX_AGE_RT_A-filtered-2024-10-30.csv"
df = pd.read_csv(ruta)
#Se revisan las primeras filas del archivo
df.head() ref_area.label ... note_source.label
0 Afghanistan ... Repository: ILO-STATISTICS - Micro data proces...
1 Afghanistan ... Repository: ILO-STATISTICS - Micro data proces...
2 Afghanistan ... Repository: ILO-STATISTICS - Micro data proces...
3 Afghanistan ... Repository: ILO-STATISTICS - Micro data proces...
4 Afghanistan ... Repository: ILO-STATISTICS - Micro data proces...
[5 rows x 11 columns]
#Se filtra por las columnas de interés
df = df[['ref_area.label', 'sex.label', 'classif1.label','time', 'obs_value']]
#Se filtra por sexo = total y mayores de 25 años
df = df[(df['sex.label'] == 'Sex: Total') & (df['classif1.label'] == 'Age (Youth, adults): 25+')]
#Se elimina la columna sex.label, ya no es necesaria
df.drop(['sex.label', 'classif1.label'], axis = 1, inplace = True)
#Se revisan los paises en ref_area.label, ya que se quiere mostrar por separado y no agrupaciones
print(df['ref_area.label'].unique())['Afghanistan' 'Angola' 'Albania' 'United Arab Emirates' 'Argentina'
'Armenia' 'Australia' 'Austria' 'Azerbaijan' 'Burundi' 'Belgium' 'Benin'
'Burkina Faso' 'Bangladesh' 'Bulgaria' 'Bahamas' 'Bosnia and Herzegovina'
'Belarus' 'Belize' 'Bermuda' 'Bolivia (Plurinational State of)' 'Brazil'
'Barbados' 'Brunei Darussalam' 'Bhutan' 'Botswana' 'Canada' 'Switzerland'
'Chile' "Côte d'Ivoire" 'Cameroon' 'Congo, Democratic Republic of the'
'Cook Islands' 'Colombia' 'Comoros' 'Cabo Verde' 'Costa Rica' 'Curaçao'
'Cayman Islands' 'Cyprus' 'Czechia' 'Germany' 'Djibouti' 'Denmark'
'Dominican Republic' 'Algeria' 'Ecuador' 'Egypt' 'Spain' 'Estonia'
'Ethiopia' 'Finland' 'Fiji' 'Falkland Islands, Malvinas' 'France'
'Micronesia (Federated States of)'
'United Kingdom of Great Britain and Northern Ireland' 'Georgia' 'Ghana'
'Guinea' 'Gambia' 'Guinea-Bissau' 'Greece' 'Grenada' 'Guatemala' 'Guyana'
'Hong Kong, China' 'Honduras' 'Croatia' 'Hungary' 'Indonesia' 'India'
'Ireland' 'Iran (Islamic Republic of)' 'Iraq' 'Iceland' 'Israel' 'Italy'
'Jamaica' 'Jordan' 'Japan' 'Kazakhstan' 'Kenya' 'Kyrgyzstan' 'Cambodia'
'Kiribati' 'Republic of Korea' 'Kosovo' 'Kuwait'
"Lao People's Democratic Republic" 'Lebanon' 'Liberia' 'Saint Lucia'
'Sri Lanka' 'Lesotho' 'Lithuania' 'Luxembourg' 'Latvia' 'Macao, China'
'Morocco' 'Monaco' 'Republic of Moldova' 'Madagascar' 'Maldives' 'Mexico'
'Marshall Islands' 'North Macedonia' 'Mali' 'Malta' 'Myanmar'
'Montenegro' 'Mongolia' 'Mozambique' 'Mauritania' 'Montserrat'
'Mauritius' 'Malawi' 'Malaysia' 'Namibia' 'New Caledonia' 'Niger'
'Nigeria' 'Nicaragua' 'Niue' 'Netherlands' 'Norway' 'Nepal' 'Nauru'
'New Zealand' 'Oman' 'Pakistan' 'Panama' 'Peru' 'Philippines' 'Palau'
'Papua New Guinea' 'Poland' 'Portugal' 'Paraguay'
'Occupied Palestinian Territory' 'Qatar' 'Réunion' 'Romania'
'Russian Federation' 'Rwanda' 'Saudi Arabia' 'Sudan' 'Senegal'
'Singapore' 'Sierra Leone' 'El Salvador' 'San Marino' 'Somalia' 'Serbia'
'Sao Tome and Principe' 'Suriname' 'Slovakia' 'Slovenia' 'Sweden'
'Eswatini' 'Seychelles' 'Chad' 'Togo' 'Thailand' 'Tajikistan' 'Tokelau'
'Timor-Leste' 'Tonga' 'Trinidad and Tobago' 'Tunisia' 'Türkiye' 'Tuvalu'
'Taiwan, China' 'Tanzania, United Republic of' 'Uganda' 'Ukraine'
'Uruguay' 'United States of America' 'Uzbekistan'
'Venezuela (Bolivarian Republic of)' 'Viet Nam' 'Vanuatu'
'Wallis and Futuna' 'Samoa' 'World' 'World: Low income'
'World: Lower-middle income' 'World: Upper-middle income'
'World: High income' 'Africa' 'Africa: Low income'
'Africa: Lower-middle income' 'Africa: Upper-middle income'
'Northern Africa' 'Northern Africa: Lower-middle income'
'Sub-Saharan Africa' 'Sub-Saharan Africa: Low income'
'Sub-Saharan Africa: Lower-middle income'
'Sub-Saharan Africa: Upper-middle income' 'Central Africa'
'Eastern Africa' 'Southern Africa' 'Western Africa' 'Americas'
'Americas: Lower-middle income' 'Americas: Upper-middle income'
'Americas: High income' 'Latin America and the Caribbean'
'Latin America and the Caribbean: Lower-middle income'
'Latin America and the Caribbean: Upper-middle income'
'Latin America and the Caribbean: High income' 'Caribbean'
'Central America' 'South America' 'Northern America'
'Northern America: High income' 'Arab States'
'Arab States: Lower-middle income' 'Arab States: Upper-middle income'
'Arab States: High income' 'Asia and the Pacific'
'Asia and the Pacific: Low income'
'Asia and the Pacific: Lower-middle income'
'Asia and the Pacific: Upper-middle income'
'Asia and the Pacific: High income' 'Eastern Asia'
'Eastern Asia: High income' 'South-Eastern Asia and the Pacific'
'South-Eastern Asia and the Pacific: Lower-middle income'
'South-Eastern Asia and the Pacific: Upper-middle income'
'South-Eastern Asia and the Pacific: High income' 'South-Eastern Asia'
'Pacific Islands' 'Southern Asia' 'Southern Asia: Lower-middle income'
'Europe and Central Asia' 'Europe and Central Asia: Lower-middle income'
'Europe and Central Asia: Upper-middle income'
'Europe and Central Asia: High income'
'Northern, Southern and Western Europe'
'Northern, Southern and Western Europe: Upper-middle income'
'Northern, Southern and Western Europe: High income' 'Northern Europe'
'Southern Europe' 'Western Europe' 'Eastern Europe'
'Eastern Europe: Upper-middle income' 'Eastern Europe: High income'
'Central Asia' 'Central and Western Asia: Lower-middle income'
'Central and Western Asia: Upper-middle income'
'Central and Western Asia: High income' 'Central and Western Asia'
'Western Asia' 'European Union 28' 'G20' 'ASEAN' 'BRICS'
'World excluding BRICS' 'G7' 'MENA' 'Arab League' 'CARICOM'
'European Union 27' 'Arab States: Low income' 'APEC'
'World excluding India and China'
'World: Lower-middle income excluding India'
'World: Upper-middle income excluding China' 'Yemen' 'South Africa'
'Zambia' 'Zimbabwe']
'''
Se observa que en esta lista no solo hay países, sino también regiones clasificadas por ingresos (World: Low Income,
Africa: Lower-middle income...), regiones globales y continentes (World, Africa, Americas...), grupos de países y
organizaciones (European Union 28. G20, ASEAN...), otros grupos de exclusión (World excluding India and China...) y
territorios y áreas especiales (Bermuda, Cook Islands, Hong Kong, China...). Por lo tanto, como se quiere analizar
púramente paises, se procede a filtrar para excluir a todos estos grupos.
''''\nSe observa que en esta lista no solo hay países, sino también regiones clasificadas por ingresos (World: Low Income, \nAfrica: Lower-middle income...), regiones globales y continentes (World, Africa, Americas...), grupos de países y\norganizaciones (European Union 28. G20, ASEAN...), otros grupos de exclusión (World excluding India and China...) y \nterritorios y áreas especiales (Bermuda, Cook Islands, Hong Kong, China...). Por lo tanto, como se quiere analizar \npúramente paises, se procede a filtrar para excluir a todos estos grupos.\n'
exclusion_list = [
"World", "Africa", "Americas", "Arab States", "Asia and the Pacific", "Europe and Central Asia",
"Latin America and the Caribbean", "Northern America", "Eastern Asia", "South-Eastern Asia",
"South-Eastern Asia and the Pacific", "Southern Asia", "Northern, Southern and Western Europe",
"Northern Europe", "Southern Europe", "Western Europe", "Eastern Europe", "Central Asia",
"Central and Western Asia", "Western Asia", "Pacific Islands", "Caribbean", "Central America",
"South America", "Northern Africa", "Sub-Saharan Africa", "Central Africa", "Eastern Africa",
"Southern Africa", "Western Africa", "European Union 28", "European Union 27", "G20", "G7",
"ASEAN", "BRICS", "World excluding BRICS", "APEC", "Arab League", "CARICOM", "MENA",
"World excluding India and China", "World: Lower-middle income excluding India",
"World: Upper-middle income excluding China", "Bermuda", "Cook Islands", "Curaçao", "Cayman Islands",
"Falkland Islands, Malvinas", "Hong Kong, China", "Macao, China", "Montserrat", "New Caledonia",
"Niue", "Occupied Palestinian Territory", "Réunion", "Tokelau", "Taiwan, China", "Wallis and Futuna",
"South Africa"
]
# Filtrar el DataFrame excluyendo los elementos de la lista y aquellos que contienen ":"
df = df[~df['ref_area.label'].isin(exclusion_list) & ~df['ref_area.label'].str.contains(":")]
#df = df[(~df['ref_area.label'].str.contains(":")) & (~df['ref_area.label'].str.contains("world", case = False))]
#Comprobemos ahora el mínimo y máximo de los años de registros
print(f'Mínimo año: {df['time'].min()}')Mínimo año: 2014
print(f'Máximo año: {df['time'].max()}')Máximo año: 2023
#Se almacenan en variables pero se reduce el rango para que no se estudien los extremos. Esto se hace para no reducir demasiado el df.
min_anio = df['time'].min() + 1
max_anio = df['time'].max() - 1
#Se filtra de forma que solo se tengan estos dos valores
df = df[(df['time'] == min_anio) | (df['time'] == max_anio)]
#Se eliminan los paises que no tengan ambos años registrados
country_list = set(df['ref_area.label'].tolist())
ind_to_drop = []
for country in country_list:
filter = df['ref_area.label'] == country
df_ = df[filter]
if min_anio not in df_['time'].values or max_anio not in df_['time'].values:
ind_to_drop += df[filter].index.to_list()
df.drop(ind_to_drop, axis = 0, inplace = True)
#Se visualiza la cantidad de países restantes
print(f'Cantidad de países a mostrar: {df.shape[0]}')Cantidad de países a mostrar: 178
#Se pivota sobre el año para facilitar la codificación de la visualización y realizar algún cambio adicional
df = df.pivot(index = 'ref_area.label', columns = 'time', values = 'obs_value').reset_index()
#Se visualiza la nueva forma de la tabla
print(df.head())time ref_area.label 2015 2022
0 Australia 4.626 2.831
1 Austria 5.054 4.323
2 Azerbaijan 3.810 4.600
3 Belgium 7.304 4.654
4 Bhutan 1.340 3.511
#Se va a calcular la diferencia entre 2014 y 2024 para ver qué países han sido críticos en el cambio
df['diff'] = round(df[max_anio] - df[min_anio], 2)
#Se ordena según el valor absoluto de la diferencia
df = df.sort_values(by = 'diff', key = lambda x: x.abs(), ascending = False)
#Se actualiza la lista de países ordenados según el valor del último año (por motivos de posicion de anotaciones en graficación)
country_list = set(df.sort_values(by = 'diff')['ref_area.label'].to_list())
offset = 5 # para evitar superposiciones en las anotaciones
#Se grafica el slope chart
fig, ax = plt.subplots(figsize = (8, 10))
annotations = []
for country in country_list:
y1 = df[min_anio][df['ref_area.label'] == country].values[0]
y2 = df[max_anio][df['ref_area.label'] == country].values[0]
if country in df['ref_area.label'].head(10).values:
diff = df['diff'][df['ref_area.label'] == country].values[0]
#Anotaciones para indicar la diferencia y el país
offset *= -1 # se va alternando hacia donde se mueve el texto verticalmente
ann = ax.annotate(f'{country}: {diff} %',
(1, y2),
textcoords = 'offset points',
xytext = (15, 0),
ha = 'left',
fontsize = 7,
fontfamily = 'monospace',
color = 'black',
arrowprops = dict(
arrowstyle = '->',
color = 'black'
))
annotations.append(ann)
if diff > 0:
params = {
'color': 'red',
'alpha': 0.7,
'marker': 'o',
'linewidth': 3,
}
else:
params = {
'color': 'green',
'alpha': 0.7,
'marker': 'o',
'linewidth': 3,
}
if diff == df.head(1)['diff'].values[0]:
params['color'] = 'darkblue'
else:
params = {
'color': 'grey',
'alpha': 0.1
}
plt.plot([0, 1], [y1, y2], **params)
#Se comprueba que no hayan anotaciones pisándose
changed = []
for i, ann1 in enumerate(annotations):
pos_1 = ann1.xy
if ann1.get_text() not in changed: #Solo sigue si no se ha movido este annotation ya
for j, ann2 in enumerate(annotations):
if i != j: # Evitar cruzamientos consigo mismo
pos_2 = ann2.xy
diff_y = abs(pos_1[1] - pos_2[1])
if diff_y < 0.5:
newPosition = (pos_2[0] + 20, pos_2[1] - 12)
ann2.set_position(newPosition)
#Se a la lista de cambiados, para que no se vuelva a mover ni este ni su "pareja"
changed.append(ann2.get_text())
#Anotaciones explicativas
text = f"""Top 10 países con la mayor variación
de tasa de desempleo entre {min_anio} y {max_anio}.
{df.head(1)['ref_area.label'].values[0]} marca la diferencia con una variación
de un {df.head(1)['diff'].values[0]}%"""
text_params = {
'color': 'black',
'ha': 'left',
'va': 'bottom',
'fontsize': 7,
'fontweight': 'bold',
'fontfamily': 'monospace'
}
ax.text(0.2, df[max_anio].max()+13, text, **text_params)
#Se añaden líneas verticales para marcar inicio y fin
params_lines = {
'color': 'black',
'alpha': 0.7,
'linewidth': 0.7,
'linestyle': ':'
}
ax.axvline(0, **params_lines)
ax.axvline(1, **params_lines)
#Se agregan los años de comparación
text_params['ha'] = 'center'
text_params['fontsize'] = 10
ax.text(0, df[min_anio].max() + 1.8, min_anio, **text_params)
ax.text(1, df[min_anio].max() + 1.8, max_anio, **text_params)
ax.axis('off')(np.float64(-0.05), np.float64(1.05), np.float64(-1.3185499999999999), np.float64(29.66955))
ax.set_title("Comparación del ratio de desempleo entre 2015 y 2022",
fontfamily = 'monospace', fontweight = 'bold', pad = 30)
plt.tight_layout()
plt.show()Audiencia: Políticos
Objetivo: Mostrar aquellos países del mundo donde se ha producido un mayor cambio en cuanto a la creación de empleo entre 2015 y 2023.
Visualización: Se utiliza Matplotlib de Python. El tipo de gráfica que se ha escogido es un Slope Chart. Se ha resaltado el top 10 de los países con mayor variación entre los años escogidos. Se utilizan distintos colores para resaltar al que ha tenido un mayor cambio con respecto a los demás. Además, se añaden anotaciones para ver de qué países se trata y el cambio producido.
Conjunto de datos: Se ha escogido una fuente proveniente del tema del día. Se ha hecho un buen preprocesamiento, donde se ha ido filtrando por los datos y columnas de interés con la librería de Pandas. Se podrían haber escogido otras fechas más extremas, como 2014 y 2024. Sin embargo, la cantidad de datos se reducía drásticamente ya que muchos de los países no disponían de datos en esas fechas. El indicador sobre el que se ha calculado la diferencia es el ratio de desempleo, por la SDG (Sustainable Development Goals). Fuente:https://ilostat.ilo.org/topics/sdg/#
Categoría: “Uncertainties”
Día 25: Global Change
import pandas as pd
import plotly.express as px
#Se carga el dataframe
ruta = "datasets/SDG_0852_SEX_AGE_RT_A-filtered-2024-10-30_mod.csv"
df = pd.read_csv(ruta)
#Se revisan las primeras filas del archivo
print(df.head()) ref_area.label time obs_value
0 Afghanistan 2021 4.516
1 Afghanistan 2020 9.792
2 Afghanistan 2017 8.318
3 Afghanistan 2014 6.773
4 Angola 2021 11.015
#Se reorganizan los datos para poder graficarse con plotly
#Se eliminan los na para poder representarse sin problemas
df.dropna(axis = 0, inplace = True)
#Se almacenan en variables pero se reduce el rango para que no se estudien los extremos. Esto se hace para no reducir demasiado el df.
min_anio = df['time'].min() + 1
max_anio = df['time'].max() - 1
#Se filtra el df para excluir los años más extremos
df = df[(df['time']>=min_anio)&(df['time']<=max_anio)]
#Se eliminan los paises que no tengan todos los años intermedios registrados
country_list = set(df['ref_area.label'].tolist())
ind_to_drop = []
for country in country_list:
filter = df['ref_area.label'] == country
df_ = df[filter]
for anio in range(min_anio, max_anio + 1):
if anio not in df_['time'].values:
ind_to_drop += df[filter].index.to_list()
break
df.drop(ind_to_drop, axis = 0, inplace = True)
#Se visualiza la cantidad de países restantes
print(f'Cantidad de países a mostrar: {df.shape[0]}')Cantidad de países a mostrar: 576
#Se ordena por año
df = df.sort_values(by = 'time')
def plot_animation():
global df
#Se crea un gráfico de burbujas animado
fig = px.scatter(df, x = df['time'], y = df['obs_value'],
size = df['obs_value'], color = df['ref_area.label'],
animation_frame = df['time'], hover_name = df['ref_area.label'])
fig.update_layout(xaxis_range = [2013, 2023],
yaxis_range = [0, 35],
width=800,
height=600,
showlegend = False)
return fig
fig = plot_animation()
fig.show()Audiencia: Políticos
Objetivo: En conjunción con la gráfica anterior, se muestra como se han ido comportando los ratios de desempleo para los adultos de más de 25 años en los distintos países.
Visualización: Se escoge una visualización animada de Plotly en Python. Concretamente, se anima un Scatter Plot de Burbujas, de forma que se vea según el tamaño una tasa de desempleo mayor o menor y cómo esta va variando.
Conjunto de datos: Se ha aprovechado la misma fuente que la gráfica anterior a partir de un punto de procesamiento y se ha transformado en consonancia con los requerimientos de esta gráfica. Fuente:https://ilostat.ilo.org/topics/sdg/#
Día 26: AI
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.dates import date2num, DateFormatter
import matplotlib.dates as mdates
# Generar datos de ejemplo
np.random.seed(0)
days = pd.date_range(start='2024-01-01', periods=30, freq='D')
open_prices = np.random.uniform(low=10, high=50, size=len(days))
close_prices = open_prices + np.random.uniform(low=-10, high=10, size=len(days))
high_prices = np.maximum(open_prices, close_prices) + np.random.uniform(low=0, high=5, size=len(days))
low_prices = np.minimum(open_prices, close_prices) - np.random.uniform(low=0, high=5, size=len(days))
data = pd.DataFrame({
'Date': days,
'Open': open_prices,
'Close': close_prices,
'High': high_prices,
'Low': low_prices
})
# Ajustar el formato de fecha
data['Date'] = pd.to_datetime(data['Date'])
data['Date'] = date2num(data['Date']) # Convertir a formato numérico
# Crear el gráfico
plt.figure(figsize=(12, 6), facecolor='black')
plt.title('Gráfico Candlestick de IAforPlotsWithGPT', color='white')
plt.grid(color='gray', linestyle='--', alpha=0.5)
# Dibujar las velas
for index in range(len(data)):
if data['Close'][index] > data['Open'][index]:
color = 'green' # Alcista
lower = data['Open'][index]
height = data['Close'][index] - data['Open'][index]
else:
color = 'red' # Bajista
lower = data['Close'][index]
height = data['Open'][index] - data['Close'][index]
plt.bar(data['Date'][index], height, bottom=lower, color=color, alpha=0.7, width=0.5)
plt.plot([data['Date'][index], data['Date'][index]], [data['Low'][index], data['High'][index]], color=color, alpha=0.7)
# Configurar los ejes
plt.ylabel('Cantidad en Euros', color='white')
plt.xlabel('Días', color='white')
plt.xticks(data['Date'], [day.strftime('%Y-%m-%d') for day in days], rotation=45, color='white')([<matplotlib.axis.XTick object at 0x000002E1A3D01220>, <matplotlib.axis.XTick object at 0x000002E1A3D028D0>, <matplotlib.axis.XTick object at 0x000002E1A38CE450>, <matplotlib.axis.XTick object at 0x000002E1A3B85940>, <matplotlib.axis.XTick object at 0x000002E1A3B54E00>, <matplotlib.axis.XTick object at 0x000002E1A3B843E0>, <matplotlib.axis.XTick object at 0x000002E1A3B84590>, <matplotlib.axis.XTick object at 0x000002E1A3B56C00>, <matplotlib.axis.XTick object at 0x000002E1A3B57230>, <matplotlib.axis.XTick object at 0x000002E1A3B57FB0>, <matplotlib.axis.XTick object at 0x000002E1A3DC94C0>, <matplotlib.axis.XTick object at 0x000002E1A3BD9610>, <matplotlib.axis.XTick object at 0x000002E1A3B568A0>, <matplotlib.axis.XTick object at 0x000002E1A3B2F8F0>, <matplotlib.axis.XTick object at 0x000002E1A3B56180>, <matplotlib.axis.XTick object at 0x000002E1A3B2F860>, <matplotlib.axis.XTick object at 0x000002E1F8F618E0>, <matplotlib.axis.XTick object at 0x000002E1A3B2F410>, <matplotlib.axis.XTick object at 0x000002E1A3B2D970>, <matplotlib.axis.XTick object at 0x000002E1A3B2CFE0>, <matplotlib.axis.XTick object at 0x000002E1A3B2D7C0>, <matplotlib.axis.XTick object at 0x000002E1F89001A0>, <matplotlib.axis.XTick object at 0x000002E1A3B2D070>, <matplotlib.axis.XTick object at 0x000002E1A3B57B90>, <matplotlib.axis.XTick object at 0x000002E1A3B2D520>, <matplotlib.axis.XTick object at 0x000002E1F8903920>, <matplotlib.axis.XTick object at 0x000002E1F8902150>, <matplotlib.axis.XTick object at 0x000002E1A3B2FEF0>, <matplotlib.axis.XTick object at 0x000002E1F89013D0>, <matplotlib.axis.XTick object at 0x000002E1F8900590>], [Text(19723.0, 0, '2024-01-01'), Text(19724.0, 0, '2024-01-02'), Text(19725.0, 0, '2024-01-03'), Text(19726.0, 0, '2024-01-04'), Text(19727.0, 0, '2024-01-05'), Text(19728.0, 0, '2024-01-06'), Text(19729.0, 0, '2024-01-07'), Text(19730.0, 0, '2024-01-08'), Text(19731.0, 0, '2024-01-09'), Text(19732.0, 0, '2024-01-10'), Text(19733.0, 0, '2024-01-11'), Text(19734.0, 0, '2024-01-12'), Text(19735.0, 0, '2024-01-13'), Text(19736.0, 0, '2024-01-14'), Text(19737.0, 0, '2024-01-15'), Text(19738.0, 0, '2024-01-16'), Text(19739.0, 0, '2024-01-17'), Text(19740.0, 0, '2024-01-18'), Text(19741.0, 0, '2024-01-19'), Text(19742.0, 0, '2024-01-20'), Text(19743.0, 0, '2024-01-21'), Text(19744.0, 0, '2024-01-22'), Text(19745.0, 0, '2024-01-23'), Text(19746.0, 0, '2024-01-24'), Text(19747.0, 0, '2024-01-25'), Text(19748.0, 0, '2024-01-26'), Text(19749.0, 0, '2024-01-27'), Text(19750.0, 0, '2024-01-28'), Text(19751.0, 0, '2024-01-29'), Text(19752.0, 0, '2024-01-30')])
plt.yticks(color='white')(array([-10., 0., 10., 20., 30., 40., 50., 60., 70.]), [Text(0, -10.0, '−10'), Text(0, 0.0, '0'), Text(0, 10.0, '10'), Text(0, 20.0, '20'), Text(0, 30.0, '30'), Text(0, 40.0, '40'), Text(0, 50.0, '50'), Text(0, 60.0, '60'), Text(0, 70.0, '70')])
plt.gca().xaxis.set_major_formatter(DateFormatter('%Y-%m-%d'))
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=2))
plt.gca().set_facecolor('black')
# Mostrar el gráfico
plt.tight_layout()
plt.show()Audiencia: Estudiantes de IA
Objetivo: Mostrar lo eficiente que puede ser usar Chatgpt como creador de gráficas de forma rápida y usando el lenguaje más alto que existe de programación: el lenguaje natural.
Visualización: Se utiliza un “CandleStick”, o gráfico de velas, con Matplotlib de Python.
Conjunto de datos: El gráfico ha sido generado artificialmente con Chatgpt usando el siguiente prompt:
Objetivo: Quiero que me generes solamente con Matplotlib y Seaborn en Python un gráfico tipo “Candlestick”.
Contexto: este gráfico representará los valores en el mercado así como sus precios de Inicio y cierre, máximos y mínimos, de una moneda virtual imaginaria llamada IAforPlotsWithGPT.
Detalles de la gráfica:
- Eje y: cantidad en euros
- Eje x: días del 01/01/2024 hasta 30 días después.
- Colores de las velas: los días alcistas en verde y los bajistas en rojo, con una opacidad de alpha = 0.7
- Muestra el grid
- El gráfico tendrá el fondo oscuro.
Día 27: Good/Bad
import pandas as pd
import numpy as np
import plotly.graph_objects as go
#Se carga el archivo
ruta = r"datasets\Student_Satisfaction_Survey.csv"
df = pd.read_csv(ruta, encoding = 'latin1')
#Se revisan las primeras filas del archivo
print(df.info())<class 'pandas.core.frame.DataFrame'>
RangeIndex: 580 entries, 0 to 579
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 SN 580 non-null int64
1 Total Feedback Given 580 non-null int64
2 Total Configured 580 non-null int64
3 Questions 580 non-null object
4 Weightage 1 580 non-null int64
5 Weightage 2 580 non-null int64
6 Weightage 3 580 non-null int64
7 Weightage 4 580 non-null int64
8 Weightage 5 580 non-null int64
9 Average/ Percentage 580 non-null object
10 Course Name 580 non-null object
11 Basic Course 580 non-null object
dtypes: int64(8), object(4)
memory usage: 54.5+ KB
None
#Se toma la suma total de cada campo de resultados de encuesta
muy_malo = df['Weightage 1'].sum()
malo = df['Weightage 2'].sum()
normal = df['Weightage 3'].sum()
bueno = df['Weightage 4'].sum()
muy_bueno = df['Weightage 5'].sum()
#Se calcula una puntuación promedio del 1 al 5, de peor a mejor
puntuaciones = [1] * muy_malo + [2] * malo + [3] * normal + [4] * bueno + [5] * muy_bueno
#Se calcula el valor de la encuesta
promedio = round(np.mean(puntuaciones),1)
# Crear el gráfico de medidor
fig = go.Figure(go.Indicator(
mode="gauge+number",
value=promedio,
title={'text': "Encuesta de Satisfacción"},
gauge={
'axis': {'range': [1, 5], 'tickvals': [1, 2, 3, 4, 5], 'ticktext': ['Muy Malo', 'Malo', 'Neutral', 'Bueno', 'Muy Bueno']},
'bar': {'color': "rgba(255, 255, 0, 0.7)"},
'steps': [
{'range': [1, 2], 'color': "rgba(255, 0, 0, 0.7)"},
{'range': [2, 4], 'color': "rgba(0, 0, 255, 0.7)"},
{'range': [4, 5], 'color': "rgba(0, 255, 0, 0.7)"},
]
}
))
# Mostrar el gráfico
fig.show()Audiencia: Coordinador de un Master Universitario
Objetivo: Mostrar el grado de satisfacción general de los alumnos de un master
Visualización: Se utiliza el tipo de gráfico Indicador de la librería Plotly de Python.
Conjunto de datos: Se realiza un recuento de las distintas categorías de satisfacción y se calcula la puntuación media. Fuente:https://www.kaggle.com/datasets/prasad22/student-satisfaction-survey
Día 28: Trend
Audiencia: Economistas y cargos políticos
Objetivo: Enfatizar en la necesidad de disminuir la desigualdad de ingresos con el fin de yo sobrepasar ciertos niveles de GINI ya que, parece ser, existe una relación entre el ratio de delincuencia y este índice.
Visualización: Se escoge la herramienta Datawrapper con el “Scatter plot”. Se ha dividido la gráfica por zonas de peligrosidad según la distribución y la guía de una línea de tendencia cúbica, que es la que parece que mejor se ajusta a la distribución.
Conjunto de datos: No se ha necesitado realizar preprocesamiento más allá de la selección de las columnas adecuadas. Fuente:https://github.com/fivethirtyeight/data/blob/master/hate-crimes/README.md
Día 29: Black’n’White
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer
import re
from wordcloud import WordCloud
import matplotlib.pyplot as plt
def preprocess_text(text):
# Convertir a minúsculas
text = text.lower()
# Eliminar caracteres especiales y números
text = re.sub(r"[^a-zA-Z\s]", "", text)
# Tokenizar el texto
tokens = word_tokenize(text)
# Eliminar palabras vacías (stopwords)
stop_words = set(stopwords.words("english"))
tokens = [word for word in tokens if word not in stop_words]
# Lematización
lemmatizer = WordNetLemmatizer()
tokens = [lemmatizer.lemmatize(word) for word in tokens]
# Unir tokens en un solo texto procesado
processed_text = " ".join(tokens)
return processed_text
# Leer el archivo de texto
ruta = r"C:\Users\Cristian\Documents\5- Educación\6- Master Universitario en Big Data y Ciencias de Datos\2- Asignaturas\7. Visualización de datos\2. Actividades\Actividad1\30daysChartChallenge\datasets\Black_or_white.txt"
with open(ruta, 'r', encoding='utf-8') as file:
texto = file.read()
# Ejemplo de uso
texto_procesado = preprocess_text(texto)
wordcloud = WordCloud(width=800, height=400).generate(texto_procesado)
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")(np.float64(-0.5), np.float64(799.5), np.float64(399.5), np.float64(-0.5))
plt.show()Audiencia: Concierto Michael Jackson
Objetivo: Decorar el escenario incluyendo este tipo de gráficas artísticas sobre sus canciones. Este es un ejemplo de una de las más populares, Black or White.
Visualización: Se utiliza la librería de wordcloud de Python para la representación de esta gráfica.
Conjunto de datos: Se utilizan técnicas de procesamiento del lenguaje natural (NPL) con la librería nltk de Python para tokenizar, eliminar palabras vacías y lematizar la canción. Fuente: https://genius.com/Michael-jackson-black-or-white-lyrics
Día 30: FiveThirtyEight (theme day)
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
#Se carga el archivo
ruta = r"datasets\avengers.csv"
df = pd.read_csv(ruta, encoding = 'latin1')
#Info
df.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 173 entries, 0 to 172
Data columns (total 21 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 URL 173 non-null object
1 Name/Alias 163 non-null object
2 Appearances 173 non-null int64
3 Current? 173 non-null object
4 Gender 173 non-null object
5 Probationary Introl 15 non-null object
6 Full/Reserve Avengers Intro 159 non-null object
7 Year 173 non-null int64
8 Years since joining 173 non-null int64
9 Honorary 173 non-null object
10 Death1 173 non-null object
11 Return1 69 non-null object
12 Death2 17 non-null object
13 Return2 16 non-null object
14 Death3 2 non-null object
15 Return3 2 non-null object
16 Death4 1 non-null object
17 Return4 1 non-null object
18 Death5 1 non-null object
19 Return5 1 non-null object
20 Notes 75 non-null object
dtypes: int64(3), object(18)
memory usage: 28.5+ KB
#Selecciono las columnas de interés
df = df[['Name/Alias', 'Appearances', 'Current?', 'Gender', 'Year', 'Years since joining', 'Years since joining',
'Honorary']]
#Creo una categoría más: outliers. Esta se va a calcular por separado, para los hombres y para las mujeres
def calc_outliers(data):
Q1, Q2, Q3 = data.quantile([0.25, 0.5, 0.75])
IQR = Q3 - Q1
lim_sup = Q3 + 1.5 * IQR
return lim_sup
#Llamo a la función para calcular los límites sup e inf de outliers según género
lim_sup_male = calc_outliers(df['Appearances'][df['Gender'] == 'MALE'])
lim_sup_fem = calc_outliers(df['Appearances'][df['Gender'] == 'FEMALE'])
#Instancio una nueva columna con valores None
df['Importance'] = None
#Ahora filtro por género y le doy un valor a la la columna creada en función de los límites de "normalidad"
df['Importance'][df['Gender']=='MALE'] = df['Appearances'][df['Gender']=='MALE'].apply(lambda x: 'VIP' if x >= lim_sup_male else 'Normal')
df['Importance'][df['Gender']=='FEMALE'] = df['Appearances'][df['Gender']=='FEMALE'].apply(lambda x: 'VIP' if x >= lim_sup_fem else 'Normal')
#Ahora se crea una nueva columna combinando el género y su importancia
df['Gender_Importance'] = df['Importance'] + '-' + df['Gender']
def plot_kdes():
#Se establece el estilo
sns.set_theme(style="white", rc={"axes.facecolor": (0, 0, 0, 0)})
# Initialize the FacetGrid object
pal = sns.cubehelix_palette(10, rot=-.25, light=.7)
g = sns.FacetGrid(df, row="Gender_Importance", hue="Gender_Importance", aspect=5, height=3, palette=pal)
# Draw the densities in a few steps
g.map(sns.kdeplot, "Appearances",
bw_adjust=.5, clip_on=False,
fill=True, alpha=0.7, linewidth=1.5)
g.map(sns.kdeplot, "Appearances", clip_on=False, color="w", lw=2, bw_adjust=.5)
# passing color=None to refline() uses the hue mapping
g.refline(y=0, linewidth=2, linestyle="-", color=None, clip_on=False)
# Define and use a simple function to label the plot in axes coordinates
def label(x, color, label):
ax = plt.gca()
ax.text(0, .2, label, fontweight="bold", color=color,
ha="left", va="center", transform=ax.transAxes)
g.map(label, "Appearances")
# Define a function to label each facet with unique text
def add_text_to_facets(g):
global df
df.sort_values(by = 'Appearances', ascending = False, inplace = True)
for ax in g.axes.flat:
# Se obtiene la importancia y género del facet
categoria = ax.get_title().replace('Gender_Importance = ', "")
# Define el TOP 3 de los personajes con más apariciones
top_3 = df['Name/Alias'][df['Gender_Importance']==categoria].head(3 ).to_list()
text = f"TOP 3: {",".join(top_3)}" # Cambia esto por el texto que desees
# Agrega el texto al facet
ax.text(0.5, 0.5, text, fontsize=12, ha='center', va='center', transform=ax.transAxes,
fontweight = 'bold', color = 'black')
# Llama a la función para agregar texto a cada facet
add_text_to_facets(g)
# Set the subplots to overlap
g.figure.subplots_adjust(hspace=-.25)
# Remove axes details that don't play well with overlap
g.set_titles("")
g.set(yticks=[], ylabel="")
g.despine(bottom=True, left=True)
return g
g = plot_kdes()
plt.show()Audiencia: Exposición sobre comics de Marvel
Objetivo: Presentar al público más aficionado a los comics de Marvel las distribuciones de cantidad de apariciones distinguido por género e importancia. Además, se resalta el top 3 de los personajes que más aparecen dentro de cada grupo.
Visualización: Se usa Seaborn de Python. El tipo de gráfica es una kde (Kernel Density Estimation) para observar el tipo de districución que se da en cada grupo. Se observa que, mientras que el grupo VIP de las mujeres la distribución está más concentrada alrededor de las 1000-1500 apariciones, en el grupo de los hombres VIP se tienen unas colas muy largas y una gráfica de probabilidad poco densa, estando los personajes muy expandidos en apariciones entre 1000 y 4500.
Conjunto de datos: Se ha realizado un preprocesamiento para detectar los outliers por apariciones y asi definir la importancia de la población diferenciada por hombres y mujeres. De esta manera, se puede graficar el kde diferenciado en cuatro categorías, lo cual da una mejor información visual. Fuente:https://github.com/fivethirtyeight/data/blob/master/avengers/avengers.csv
Principales Fuentes y referencias
Se ha usado documentación y recursos externos como apoyo para la creación de todas las gráficas en este documento, las cuales son completamente originales. Fuentes: